package org.wat.wcy.isi.mmazur.bp.io.core;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.wat.wcy.isi.mmazur.bp.io.BinaryObject;
import org.wat.wcy.isi.mmazur.bp.io.model.BusinessProcess;
import org.wat.wcy.isi.mmazur.bp.io.model.IdDesc;
import org.wat.wcy.isi.mmazur.bp.io.model.Node;
import org.wat.wcy.isi.mmazur.bp.io.model.Transition;
import org.wat.wcy.isi.mmazur.bp.io.model.enums.NodeType;
import org.wat.wcy.isi.mmazur.bp.io.model.enums.TransitionType;
import org.wat.wcy.isi.mmazur.bp.io.model.temporary.MatrixRow;
import org.wat.wcy.isi.mmazur.bp.io.model.temporary.Route;

public class IOHandler {

	private static final String INDEX_ID = "index1.dat";
	private static final String INDEX_TYPE = "index2.dat";
	private static final String FILE_NODES = "nodes.dat";
	private static final String FILE_MATRIX = "matrix.dat";
	private static final String DB_FILE = "schemas.dat";

	public void store(File projectDir, final IDataProvider dataProvider) {
		File nodesFile = new File(projectDir, FILE_NODES);
		File matrixFile = new File(projectDir, FILE_MATRIX);
		RandomAccessFile rafNodes = null;
		RandomAccessFile rafMatrix = null;
		try {
			rafNodes = new RandomAccessFile(nodesFile, "rw");
			rafMatrix = new RandomAccessFile(matrixFile, "rw");
			
			Map<String, Node> nodes = dataProvider.getNodes();
			Map<String, Node> pools = dataProvider.getPools();
			
			nodes.putAll(pools);
			
			Map<Integer, Integer> indexType = new HashMap<Integer, Integer>();
			Map<String, Integer> indexId = new HashMap<String, Integer>();
			
			Map<Route, Transition> transitions = dataProvider.getTransitions();
			List<Node> nodesList = new ArrayList<Node> (nodes.values());
			List<Node> poolsList = new ArrayList<Node> (pools.values());
			
			Collections.sort(nodesList, new Comparator<Node>() {

				@Override
				public int compare(Node o1, Node o2) {
					if (o1 != null && o2 != null) {
						int ret = o1.getType().ordinal() - o2.getType().ordinal();
						if (ret == 0){
							ret = o1.getName().compareTo(o2.getName());
						}
						return ret;
					}
					return 0;
				}
			});
			
			rafNodes.writeLong(nodes.size());
			
			NodeType tmpType = null;
			for (int index = 0; index < nodesList.size(); index++) {
				Node nd = nodesList.get(index);
				for (Node pool : poolsList) {
					if (pool.getGraphicsInfo().contains(nd.getGraphicsInfo())) {
						nd.setLaneId(new IdDesc(pool.getId().toString()));
					}
				}
				nd.write(rafNodes);
				indexId.put(nd.getId().toString(), index);
				if (tmpType == null || nd.getType() != tmpType) {
					tmpType = nd.getType();
					indexType.put(tmpType.ordinal(), index);
				}
				storeExtededNodeData(projectDir, nd);
			}
			
			storeIndexes(projectDir, nodesList, indexId, indexType);
			
			int size = nodesList.size();
			
			for (int i = 0; i < size; i++) {
				for (int j = 0; j < size; j++) {
					Transition transitionDesc = null;
					Node from = nodesList.get(i);
					Node to = nodesList.get(j);
					Route route = new Route(from.getId().toString(), to.getId().toString());
					transitionDesc = transitions.get(route);
					if (transitionDesc == null) {
						transitionDesc = new Transition();
					}
					transitionDesc.write(rafMatrix);
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (Throwable t) {
			t.printStackTrace();
		} finally {
			try {
				if (rafNodes != null) {
					rafNodes.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (rafMatrix != null) {
					rafMatrix.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void storeIndexes(File projectDir, List<Node> nodesList, Map<String, Integer> indexIdMap,
			Map<Integer, Integer> indexTypeMap) {
		File fileIndexType = new File(projectDir, INDEX_TYPE);
		RandomAccessFile rafType = null;
		try {
			rafType = new RandomAccessFile(fileIndexType, "rw");
			int typesCount = NodeType.values().length;
			for (int i = 0; i < typesCount; i++) {
				int k = -1;
				if (indexTypeMap.containsKey(i)) {
					k = indexTypeMap.get(i);
				}
				rafType.writeInt(k);
			}
		} catch (Throwable t) {
			t.printStackTrace();
		} finally {
			try {
				rafType.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		File fileIndexId = new File(projectDir, INDEX_ID);
		StringBuilder strb = new StringBuilder();
		for (Node node : nodesList) {
			String key = node.getId().toString();
			strb.append(key);
			strb.append("=");
			strb.append(indexIdMap.get(key));
			strb.append("\n");
		}
		try {
			FileUtils.writeStringToFile(fileIndexId, strb.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void storeExtededNodeData(File projectDir, Node nd) {
		if (nd == null) {
			return;
		}
		String fileName = nd.getId().toString() + ".dat";
		File file = new File(projectDir, fileName);
		Map<String, String> map = nd.getAdditionalTags();
		StringBuilder strb = new StringBuilder();
		for (String key : map.keySet()) {
			if (StringUtils.isEmpty(key)) {
				continue;
			}
			String data = String.format("%s=%s\n", key, map.get(key));
			strb.append(data);
			strb.append("=");
			String value = map.get(key);
			strb.append(StringUtils.isEmpty(value) ? "" : value);
			strb.append("\n");
		}
		try {
			FileUtils.writeStringToFile(file, strb.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public long getNodesCount(final String projectDir) {
		File nodesFile = new File(projectDir, FILE_NODES);
		RandomAccessFile raf = null;
		long count = 0;
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			count = raf.readLong();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return count;
	}
	/**
	 * 
	 * @param projectDir
	 * @param index - starts from 0
	 * @return
	 */
	public Node readNode(final String projectDir, int index) {
		File nodesFile = new File(projectDir, FILE_NODES);
		RandomAccessFile raf = null;
		Node result = new Node();
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			int skip = Node.sizeOf() * index;
			skip += (Long.SIZE / 8);
			raf.skipBytes(skip);
			result.read(raf);
			return result;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public List<BusinessProcess> readProcesses(final String storeDir) {
		return (List<BusinessProcess>)readElements(BusinessProcess.class, storeDir, DB_FILE);
	}
	
	@SuppressWarnings("unchecked")
	public List<Node> readNodes(final String projectDir) {
		return (List<Node>)readElements(Node.class, projectDir, FILE_NODES);
	}
	
	public List<? extends BinaryObject> readElements(final Class<? extends BinaryObject> clazz, final String projectDir,
			final String fileName) {
		File file = new File(projectDir, fileName);
		if (!file.exists() || file.length() == 0) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return new ArrayList<BusinessProcess> ();
		}
		RandomAccessFile raf = null;
		List<BinaryObject> result = new ArrayList<BinaryObject>();
		try {
			raf = new RandomAccessFile(file, "r");
			long n = raf.readLong();
			for (int i = 0; i < (int)n; i++) {
				try {
					BinaryObject bo = clazz.newInstance();
					bo.read(raf);
					result.add(bo);
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
			return result;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}
	
	public Transition [][] readMatrix(final String projectDir) {
		File nodesFile = new File(projectDir, FILE_MATRIX);
		RandomAccessFile raf = null;
		int n = (int)getNodesCount(projectDir);
		Transition result[][] = new Transition[n][n];
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			for (int i=0; i < n ; i++) {
				for (int j=0; j < n; j++) {
					Transition td = new Transition();
					td.read(raf);
					result[j][i] = td;
				}
			}
			return result;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	public MatrixRow readMatrixRow(final String projectDir, int i) {
		File nodesFile = new File(projectDir, FILE_MATRIX);
		RandomAccessFile raf = null;
		List<Transition> result = new ArrayList<Transition>();
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			long n = getNodesCount(projectDir);
			Transition td = new Transition();
			int size = Transition.sizeOf();
			int skip = (size * (int)n) * i;
			raf.skipBytes(skip);
			int count = 0;
			for (int k = 0; k < n; k++) {
				td = new Transition();
				td.read(raf);
				if (td.getType() != TransitionType.NULL) {
					count++;
				}
				result.add(td);
			}
			MatrixRow res = new MatrixRow(result, count);
			return res;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	public Transition readMatrixField(final String projectDir, int i, int j) {
		File nodesFile = new File(projectDir, FILE_MATRIX);
		RandomAccessFile raf = null;
		Transition result = new Transition();
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			long n = getNodesCount(projectDir);
			int size = Transition.sizeOf();
			int skip = (size * (int)n) * i + size * j;
			raf.skipBytes(skip);
			result.read(raf);
			return result;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public int readIndexId(String projectDir, String id) {
		File fileIndexType = new File(projectDir, INDEX_ID);
		List<String> lines;
		try {
			lines = FileUtils.readLines(fileIndexType);
			for (String line : lines) {
				if (StringUtils.startsWithIgnoreCase(line, id)) {
					return Integer.parseInt(line.split("[=]")[1]);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return 0;
	}
	
	@SuppressWarnings("unchecked")
	public int getNodeIndex(String projectDir, String id) {
		File fileIndexType = new File(projectDir, INDEX_ID);
		List<String> lines;
		int index = 0;
		try {
			lines = FileUtils.readLines(fileIndexType);
			for (String line : lines) {
				if (StringUtils.startsWithIgnoreCase(line, id)) {
					return index;
				}
				index++;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return 0;
	}
	
	public int readIndexType(String projectDir, NodeType type) {
		File fileIndexType = new File(projectDir, INDEX_TYPE);
		RandomAccessFile rafType = null;
		try {
			rafType = new RandomAccessFile(fileIndexType, "r");
			int skip = type.ordinal() * (int)(Integer.SIZE / 8);
			rafType.skipBytes(skip);
			return rafType.readInt();
		} catch (Throwable t) {
			t.printStackTrace();
		} finally {
			try {
				rafType.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return 0;
	}

	public MatrixRow readMatrixColumn(String projectDir, int  column) {
		File nodesFile = new File(projectDir, FILE_MATRIX);
		RandomAccessFile raf = null;
		List<Transition> result = new ArrayList<Transition>();
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			long n = getNodesCount(projectDir);
			Transition td = new Transition();
			int size = Transition.sizeOf();
			int skip = size * column;
			raf.skipBytes(skip);
			int count = 0;
			for (int k = 0; k < n - 1; k++) {
				td = new Transition();
				td.read(raf);
				if (td.getType() != TransitionType.NULL) {
					count++;
					result.add(td);
				}
				if (k < n - 1) {
					skip = size * ((int)n - 1);
					raf.skipBytes(skip);
				}
			}
			MatrixRow res = new MatrixRow(result, count);
			return res;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	public List<Node> readNodes(String projectDir, List<Integer> indexes) {
		File nodesFile = new File(projectDir, FILE_NODES);
		RandomAccessFile raf = null;
		List<Node> result = new ArrayList<Node>();
		try {
			raf = new RandomAccessFile(nodesFile, "r");
			long n = raf.readLong();
			int size = Node.sizeOf();
			for (int i = 0; i < (int)n; i++) {
				Node nd = new Node();
				if (indexes.contains(i)) {
					nd.read(raf);
					result.add(nd);
				} else {
					raf.skipBytes(size);
				}
			}
			return result;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	public Node readNode(String projectDir, String id) {
		int index = getNodeIndex(projectDir, id);
		return readNode(projectDir, index);
	}

	@SuppressWarnings("unchecked")
	public Map<String, String> getNodeExtendedData(String projectDir,
			String nodeId) {
		File f = new File(projectDir, nodeId + ".dat");
		if (!f.exists() || !f.isFile()) {
			return null;
		}
		Map<String, String> map = new HashMap<String, String> ();
		try {
			List<String> lines = FileUtils.readLines(f);
			for (String line : lines) {
				String [] s = line.split("[=]");
				if (s.length == 2) {
					if (StringUtils.isNotEmpty(s[1])) {
						map.put(s[0], s[1]);
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		return map.isEmpty() ? null : map;
	}

	public void saveProcesses(String dataDir, List<BusinessProcess> processes) {
		File nodesFile = new File(dataDir, DB_FILE);
		RandomAccessFile raf = null;
		try {
			raf = new RandomAccessFile(nodesFile, "rw");
			long n = processes.size();
			raf.writeLong(n);
			for (int i = 0; i < (int)n; i++) {
				BusinessProcess bo = processes.get(i);
				bo.write(raf);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}